<template>
  <div class="pro-table-box">
    <div class="pro-table">
      <div id="searchBox" class="search-form">
        <slot name="form">
          <el-form
            v-if="props.showForm"
            ref="searchFormRef"
            :inline="true"
            :label-width="labelWidth"
            :model="searchForm"
          >
            <el-row :class="[isUnfold && 'is-unfold', `min-rows-${minRows}`, `columns-${COLUMNS}`]">
              <!-- 动态渲染表单项 -->
              <el-col
                v-for="(item, index) in formSchema"
                :key="index"
                :style="{
                  display: isUnfold || index < visibleFormSchema.length ? 'block' : 'none',
                }"
                :span="item.span || colSpan || layout.span"
              >
                <el-form-item :label="item.label" :prop="item.field" :label-width="item.labelWidth">
                  <RenderFormItem :item="item" />
                </el-form-item>
              </el-col>
              <div style="flex: 1; white-space: nowrap">
                <el-form-item label-width="0">
                  <div :style="{ width: '100%', textAlign: 'right' }">
                    <el-button type="primary" @click="onSubmit()">
                      {{ props.confirmButtonText }}
                    </el-button>
                    <el-button @click="onReset(searchFormRef)"> 重置 </el-button>
                    <template v-if="formSchema.length > visibleFormSchema.length">
                      <el-link
                        v-if="isUnfold"
                        class="icon ml10"
                        type="primary"
                        :underline="false"
                        @click="collapse"
                      >
                        合并
                        <el-icon><i-ep-arrow-up /></el-icon>
                      </el-link>
                      <el-link
                        v-if="!isUnfold"
                        class="icon ml10"
                        type="primary"
                        :underline="false"
                        @click="collapse"
                      >
                        展开
                        <el-icon><i-ep-arrow-down /></el-icon>
                      </el-link>
                    </template>
                  </div>
                </el-form-item>
              </div>
            </el-row>
          </el-form>
        </slot>
      </div>

      <div class="data-table">
        <!-- tool -->
        <div v-if="showToolbar" class="tool-bar">
          <div class="left">
            <slot name="actions"></slot>
          </div>
          <div class="right">
            <el-link @click="drawer = true">
              <el-icon><i-ep-operation /></el-icon>
            </el-link>
            <el-drawer v-model="drawer" title="" direction="rtl" width="20%">
              <el-table :data="drawerColumns" style="width: 100%">
                <el-table-column prop="label" label="列名" />
                <el-table-column prop="name" label="显示" width="80">
                  <template #default="scope">
                    <el-switch v-model="scope.row.show" />
                  </template>
                </el-table-column>
                <!-- <el-table-column prop="address" label="列排序" /> -->
                <el-table-column prop="address" label="行排序" width="120">
                  <template #default="scope">
                    <div class="btn-wrap">
                      <el-link
                        type="primary"
                        :underline="false"
                        :disabled="scope.$index === 0"
                        @click.stop="sort(scope.row, -1)"
                      >
                        上移
                      </el-link>
                      <el-link
                        type="primary"
                        :underline="false"
                        :disabled="scope.$index === drawerColumns.length - 1"
                        style="margin-left: 20px"
                        @click.stop="sort(scope.row, 1)"
                      >
                        下移
                      </el-link>
                    </div>
                  </template>
                </el-table-column>
              </el-table>
            </el-drawer>
          </div>
        </div>
        <!-- tabs -->
        <div class="tabs-bar">
          <slot name="tabs-bar"></slot>
        </div>
        <!-- table  -->
        <div class="table-wrap">
          <el-table
            ref="tableRef"
            :data="tableData.records"
            :size="tableSize"
            :row-key="rowKey"
            highlight-current-row
            @selection-change="handleSelectionChange"
            @current-change="handleCurrentChange"
            @sort-change="(data: any) => emits('sort-change', data)"
          >
            <slot name="columns"></slot>
            <template
              v-for="({ type, width, minWidth, align, ...colProps }, index) in visibleColumns"
              :key="index"
            >
              <!-- selection -->
              <el-table-column v-if="type === 'selection'" type="selection" :width="width || 60" />
              <!-- radio -->
              <template v-else-if="type === 'radio'">
                <el-table-column :width="width || 60" :align="align || 'center'" fixed="left">
                  <template #default="scope">
                    <el-radio
                      v-model="radio"
                      :value="`${scope.$index}-${scope.row[rowKey]}`"
                      @change="() => onRadioChange(scope.row)"
                      @click="
                        (e: MouseEvent) =>
                          onRadioClick(
                            e,
                            `${scope.$index}-${scope.row[rowKey]}`,
                          )
                      "
                    ></el-radio>
                  </template>
                </el-table-column>
              </template>
              <template v-else>
                <RenderTableColumn
                  :type="type"
                  :align="align"
                  :width="width"
                  :min-width="minWidth"
                  v-bind="colProps"
                />
              </template>
            </template>
          </el-table>
        </div>
      </div>

      <div class="pagination">
        <div></div>
        <el-pagination
          v-model:current-page="searchForm.pageNum"
          v-model:page-size="searchForm.pageSize"
          background
          layout="total, sizes, prev, pager, next, jumper"
          :page-sizes="[10, 20, 30, 40, 50]"
          :total="tableData.total"
          @size-change="handlePageSizeChange"
          @current-change="handlePageCurrentChange"
        >
        </el-pagination>
      </div>
    </div>
  </div>
</template>

<script lang="tsx" setup>
import {
  ref,
  computed,
  reactive,
  onMounted,
  defineComponent,
  watch,
  onBeforeMount,
  onBeforeUnmount,
  getCurrentInstance,
} from 'vue'
import type { FormInstance } from 'element-plus'
import { useComponent } from './use-component'
import {
  ElTable,
  ElTableColumn,
  ElRadio,
  ElRadioButton,
  ElRadioGroup,
  ElSelect,
  ElOption,
} from 'element-plus'
import { StorageType, useStorage } from '@/utils/storage'
import SHA256 from 'crypto-js/sha256'
import { debounce } from 'lodash-es'
import { isNumber } from '@/utils/is'

interface Props {
  labelWidth?: string | number
  formSchema: FormSchema[]
  tableColumns: TableColumnRaw[]
  tableData: TableData
  tableSize?: 'large' | 'default' | 'small'
  // multipleSelect?: boolean;
  // selection?: boolean;
  rowKey?: string
  confirmButtonText?: string
  pageSize?: number
  showToolbar?: boolean
  formItemColumns?: number
  formItemMinRows?: number
  colSpan?: number
  showForm?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  labelWidth: 'auto',
  tableSize: 'large',
  multipleSelect: true,
  selection: false,
  rowKey: 'id',
  confirmButtonText: '查询',
  pageSize: 20,
  showToolbar: true,
  formItemColumns: 3,
  formItemMinRows: 2,
  colSpan: undefined,
  showForm: true,
})
const emits = defineEmits<{
  (e: 'search', form: Recordable): void
  (e: 'selectionChange', selected: Recordable[]): void
  (e: 'init', form: Recordable): void
  (e: 'reset', form: Recordable): void
  (e: 'sort-change', data: { column: any; prop: string; order: any }): void
}>()

const COLUMNS = ref(props.formItemColumns)
const minRows = ref(props.formItemMinRows)
const isUnfold = ref(false) //是否展开, 默认合并
const collapse = () => {
  isUnfold.value = !isUnfold.value
}

const layout = computed(() => {
  const span = 24 / COLUMNS.value
  const total = props.formSchema.reduce((acc, cur) => {
    if (cur.span) {
      return acc + cur.span / span
    } else {
      return acc + 1
    }
  }, 0)
  const totalRows = Math.ceil(total / COLUMNS.value)
  const remainder = isUnfold.value
    ? total % COLUMNS.value
    : Math.min(minRows.value * COLUMNS.value, total) % COLUMNS.value // 计算余数
  // console.log('remainder', isUnfold.value, remainder);
  // 不保留小数点，会有0.00000000001问题
  const buttonOffset = +((COLUMNS.value - remainder - 1) * span).toFixed()

  return {
    span,
    buttonOffset,
    totalRows,
    total,
  }
})

const visibleFormSchema = computed(() => {
  let currentRowSpan = 0 // 当前行已占用的span
  let rowCount = 0 // 当前行数
  const result = []
  const defaultSpan = 24 / COLUMNS.value // 默认span
  const lastRowUsableSpan = 24 - defaultSpan // 最后一行可用span

  for (const item of props.formSchema as any) {
    const span = item?.span || defaultSpan
    // 如果添加当前元素会超出24span，或者已经是第三行且添加后无法预留span=8
    if (
      currentRowSpan + span > 24 ||
      (rowCount === 2 && currentRowSpan + span > lastRowUsableSpan)
    ) {
      rowCount++
      currentRowSpan = span || 0

      // 如果是第三行且无法预留位置，停止添加
      if (rowCount === 2 && currentRowSpan > lastRowUsableSpan) break
    } else {
      currentRowSpan += span
    }

    // 只添加前两行和第三行可容纳的元素
    if (rowCount < 2 || (rowCount === 2 && currentRowSpan <= lastRowUsableSpan)) {
      result.push(item)
    } else {
      break
    }
  }
  return result
})

const columns = ref<TableColumnRaw[]>(props.tableColumns)
const searchFormRef = ref<FormInstance>()

const searchForm = reactive<Recordable>({
  pageNum: 1,
  pageSize: props.pageSize,
  total: 0,
})

const drawer = ref(false)
const drawerColumns = ref<
  {
    type?: string
    label?: string
    prop?: string
    show?: boolean
  }[]
>([])

const visibleColumns = computed(() => {
  const showColumnsSet = new Set(
    drawerColumns.value.filter((item) => item.show && item.prop != null).map((item) => item.prop),
  )

  return columns.value
    .filter(
      (item) =>
        item.show !== false &&
        (['selection', 'index', 'expand', 'radio'].includes(item.type!) ||
          (item.prop && showColumnsSet.has(item.prop))),
    )
    .sort((a, b) => {
      const aIndex = drawerColumns.value.findIndex((item) => item.prop === a.prop)
      const bIndex = drawerColumns.value.findIndex((item) => item.prop === b.prop)
      return aIndex - bIndex
    })
})

const RenderFormItem = (props: { item: FormSchema }) => {
  const { item } = props

  // clearable 默认
  if (item.componentProps && typeof item.componentProps.clearable !== 'boolean') {
    item.componentProps.clearable = true
  }

  if (item.component === 'Select') {
    return RenderSelect(item)
  }

  if (item.component === 'RadioGroup') {
    return RenderRadio(item)
  }

  const Component = useComponent(item.component)

  return Component ? (
    <Component
      v-model={searchForm[item.field]}
      {...item.componentProps}
      {...(/^select/i.test(item.component!) ? { emptyValues: [undefined] } : {})}
    />
  ) : null
}

const RenderSelect = (item: FormSchema) => {
  const componentProps = item?.componentProps
  const valueAlias = componentProps?.props?.value || 'value'
  const labelAlias = componentProps?.props?.label || 'label'
  const disabledAlias = componentProps?.props?.disabled || 'disabled'

  return (
    <ElSelect v-model={searchForm[item.field]} {...item.componentProps}>
      {componentProps?.options?.map((option) => {
        const { ...other } = option
        return (
          <ElOption
            {...other}
            disabled={option[disabledAlias || 'disabled']}
            label={option[labelAlias || 'label']}
            value={option[valueAlias || 'value']}
          ></ElOption>
        )
      })}
    </ElSelect>
  )
}

const RenderRadio = (item: FormSchema) => {
  // 如果有别名，就取别名
  const componentProps = item?.componentProps
  const valueAlias = componentProps?.props?.value || 'value'
  const labelAlias = componentProps?.props?.label || 'label'
  const disabledAlias = componentProps?.props?.disabled || 'disabled'
  const Com = (item.component === 'RadioGroup' ? ElRadio : ElRadioButton) as ReturnType<
    typeof defineComponent
  >
  return (
    <ElRadioGroup v-model={searchForm[item.field]} {...item.componentProps}>
      {componentProps?.options?.map((option) => {
        const { ...other } = option
        return (
          <Com
            {...other}
            disabled={option[disabledAlias || 'disabled']}
            label={option[labelAlias || 'label']}
            value={option[valueAlias || 'value']}
          ></Com>
        )
      })}
    </ElRadioGroup>
  )
}

const RenderTableColumn = (props: TableColumnRaw) => {
  const { type, render, renderHeader, ...colProps } = props

  const renderValue = (scope: any) => {
    const value = scope.row[colProps.prop!]
    return isNumber(value) ? value : value || '--'
  }

  // 全局默认居左
  return (
    <el-table-column
      type={type}
      align={'left'}
      show-overflow-tooltip={{
        'popper-class': 'table-popper',
      }}
      {...colProps}
    >
      {{
        default: (scope: { row: any; column: any; $index: number }) => {
          if (colProps.children) return colProps.children.map((child) => RenderTableColumn(child))
          return render ? render(scope.row[colProps.prop!], scope.row) : renderValue(scope)
        },
        header: (scope: { column: any; $index: number }) => {
          return renderHeader ? renderHeader(scope) : colProps.label
        },
      }}
    </el-table-column>
  )
}

const queryData = async () => {
  emits('search', searchForm)
}

const onSubmit = () => {
  searchForm.pageNum = 1
  queryData()
  console.log('onsbumit')
}

const onReset = (formRef: FormInstance | undefined) => {
  if (!formRef) return
  formRef.resetFields()
  emits('reset', searchForm)
}

const handlePageSizeChange = (size: number) => {
  searchForm.pageSize = size
  queryData()
  console.log('onReset')
}

const handlePageCurrentChange = (pageNo: number) => {
  searchForm.pageNum = pageNo
  queryData()
  console.log('onReset')
}

const sort = (item: any, flag = 0) => {
  const idx = drawerColumns.value.findIndex((i) => i.prop === item.prop)
  if (idx === -1) return

  const targetIndex = idx + flag
  if (targetIndex < 0 || targetIndex >= drawerColumns.value.length) {
    return
  }

  ;[drawerColumns.value[idx], drawerColumns.value[targetIndex]] = [
    drawerColumns.value[targetIndex]!,
    drawerColumns.value[idx]!,
  ]
}

const tableRef = ref<InstanceType<typeof ElTable>>()
const selectedRows = ref<Recordable[]>([])

const handleSelectionChange = (selected: Recordable[]) => {
  selectedRows.value = selected
  emits('selectionChange', selectedRows.value)
}

const handleCurrentChange = (row: Recordable) => {
  if (row) {
    //
  }
}

const radio = ref()
const onRadioChange = (selected: Recordable) => {
  selectedRows.value = selected ? [selected] : []
  emits('selectionChange', selectedRows.value)
}
const onRadioClick = (e: Event, value: string) => {
  if (radio.value === value) {
    radio.value = undefined
    selectedRows.value = []
    e.preventDefault()
    e.stopPropagation()
  }
}

watch(
  () => props.tableData.records,
  (records) => {
    console.log('tableData.records', records)
    selectedRows.value = []
    radio.value = undefined
    // const rowKey = props.rowKey;
    // selectedRows.value = records.filter((record) =>
    //   selectedRows.value.some(
    //     (selectedRow) => selectedRow[rowKey] === record[rowKey],
    //   ),
    // );
    // //
    // if (selectedRows.value.length == 0) {
    //   radio.value = undefined;
    // }
  },
)

const localStorage = useStorage('pro-table', StorageType.LocalStorage)
/**
 * 创建一个用于生成哈希键的闭包函数
 * 该闭包用于缓存哈希值和列数据，以提高性能
 * @returns {Function} 一个用于生成哈希键的函数
 */
const hashKey = (() => {
  const cache: { hash: string; columns: any } = { hash: '', columns: null }
  return () => {
    if (cache.columns === columns.value) {
      return cache.hash
    }

    try {
      if (columns.value == null) {
        throw new Error('columns.value is null or undefined')
      }

      const jsonString = JSON.stringify(columns.value)
      const hash = SHA256(jsonString).toString()

      cache.hash = hash
      cache.columns = columns.value

      return hash
    } catch (error) {
      console.error('Error generating hash:', error)
      throw error
    }
  }
})()

watch(
  () => drawerColumns.value,
  (dcolumns) => {
    localStorage.set(hashKey(), dcolumns)
  },
  { deep: true },
)

onMounted(() => {
  // 从缓存中取出列数据
  drawerColumns.value =
    localStorage.get(hashKey()) ||
    columns.value
      .map((item) => ({
        type: item.type,
        label: item.label,
        prop: item.prop,
        show: true,
      }))
      .filter((item) => !['selection', 'index', 'expand', 'radio'].includes(item.type!))
  // 初始化查询表单默认值
  props.formSchema.map((item) => {
    if (!Reflect.has(searchForm, item.field)) {
      searchForm[item.field] = item.value
    }
  })

  emits('init', searchForm)
})

// 动态计算查询表单列
const resize = debounce(() => {
  COLUMNS.value = window.innerWidth >= 1440 ? 4 : 3
}, 200)

onBeforeMount(() => {
  const instance = getCurrentInstance()
  if (`${instance?.parent?.type.__name}`.toLocaleUpperCase() === 'PAGE') {
    resize()
    addEventListener('resize', resize)
  }
})
onBeforeUnmount(() => {
  removeEventListener('resize', resize)
})

defineExpose({
  searchFormRef,
  searchParams: searchForm,
  tableRef,
  selectedRows,
  getSelectionRows() {
    return tableRef.value!.getSelectionRows()
  },
  refresh() {
    queryData()
  },
  resetPageNum() {
    searchForm.pageNum = 1
  },
})
</script>

<style lang="scss" scoped>
.pro-table-box {
  position: relative;
  height: 100%;
}
.pro-table {
  position: static;
  height: 100%;
  box-sizing: border-box;
  background-color: var(--el-fill-color-blank);
  padding-bottom: 62px;
  // overflow: auto;

  .search-form {
    :deep(.el-form) {
      padding: 15px 15px 0;
      background: #f4f8ff;
      border-radius: 4px;
    }
    :deep(.el-form-item) {
      display: flex;
      align-items: center;
      margin-right: 20px;

      .el-form-item__label {
        width: 6em;
        width: auto !important;
        text-align: right;
        &:after {
          content: '：';
          margin-right: -10px;
        }
      }
      .el-form-item__content {
        flex: 1;
      }

      .el-select,
      .el-select-v2 {
        width: 100%;
      }

      .el-date-editor.el-range-editor {
        width: 100%;

        .el-range-separator {
          padding: 0;
        }
      }
    }

    :deep(.el-row) {
      @media only screen and (min-width: 1920px) {
        .el-col-xl-5 {
          width: 20%;
        }
      }
    }
  }

  .data-table {
    .tool-bar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 15px 0;

      .right {
        .el-icon {
          cursor: pointer;
        }
      }
    }

    .tabs-bar {
      position: sticky;
      top: -20px;
      background: #fff;
      z-index: 3;

      &:empty {
        display: none;
      }
    }

    :deep(.el-table) {
      .el-table__header th {
        height: 40px;
        font-size: 14px;
        font-weight: bold;
        color: #222222;
        background: #fafafa;
      }

      .el-table__row {
        height: 40px;
        font-size: 13px;
      }

      .el-table__cell {
        border-color: transparent;
        border-bottom-color: #f2f6ff;
        padding: 0;
        color: #222222;
      }

      // 设置 el-table 中 header 文字不换行，并省略
      .el-table__header .el-table__cell > .cell {
        white-space: nowrap;
      }

      .el-table__expand-icon .el-icon {
        background: url('')
          center center no-repeat;
        background-size: 85% 85%;

        svg {
          display: none;
        }
      }

      .el-table__body {
        padding-bottom: 5px;
      }

      .el-table__empty-block {
        padding: 5px 0 15px;

        .table-empty {
          line-height: 30px;
        }
      }

      // table 中 image 图片样式
      .table-image {
        width: 50px;
        height: 50px;
        border-radius: 50%;
      }

      .el-table-fixed-column--right {
        right: -1px !important;

        .cell {
          white-space: nowrap;
          padding: 0 20px;

          .el-button.is-link {
            font-size: 13px;
          }
        }
      }

      &.table-top {
        .el-scrollbar {
          margin-top: 10px;
        }
      }

      .cell {
        .el-switch--small .el-switch__label * {
          font-size: 13px;
        }
      }
    }
  }

  .pagination {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    justify-content: space-between;
    background-color: #fff;
  }

  :deep(.el-table--fit .el-table__inner-wrapper::before) {
    display: none;
  }
  :deep(.el-date-editor.el-input, .el-date-editor.el-input__wrapper) {
    width: 100%;
  }
}
</style>
<style lang="scss">
.table-columns-popper {
  height: 320px;
  overflow-y: hidden;
  overflow-x: hidden;
  .columns-item {
    width: 200px;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .label {
    display: -webkit-box;
    width: 8em;
    overflow: hidden;
    line-clamp: 1;
    text-overflow: ellipsis;
    -webkit-box-orient: vertical;
  }
  .btn-wrap {
    font-size: 12px;
    line-height: 12px;
    .el-link {
      margin: 0 4px;
    }
  }
}
.table-popper {
  max-width: 540px;
  word-wrap: break-word;
  word-break: break-all;
}
</style>
